#region License
// Copyright 2007-2008 Rory Plaire (codekaizen@gmail.com)
/*
 * This license governs use of the accompanying software. If you use the software, you
 * accept this license. If you do not accept the license, do not use the software.
 * 
 * 1. Definitions
 * The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
 * same meaning here as under U.S. copyright law.
 * A "contribution" is the original software, or any additions or changes to the software.
 * A "contributor" is any person that distributes its contribution under this license.
 * "Licensed patents" are a contributor's patent claims that read directly on its contribution.
 * 
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, including the license conditions 
 *     and limitations in section 3, each contributor grants you a non-exclusive, worldwide, 
 *     royalty-free copyright license to reproduce its contribution, prepare derivative works 
 *     of its contribution, and distribute its contribution or any derivative works that you create.
 * (B) Patent Grant- Subject to the terms of this license, including the license conditions and 
 *     limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free 
 *     license under its licensed patents to make, have made, use, sell, offer for sale, import, 
 *     and/or otherwise dispose of its contribution in the software or derivative works of the 
 *     contribution in the software.
 * 
 * 3. Conditions and Limitations
 * (A) No Trademark License- This license does not grant you rights to use any contributors' 
 *     name, logo, or trademarks.
 * (B) If you bring a patent claim against any contributor over patents that you claim are 
 *     infringed by the software, your patent license from such contributor to the software 
 *     ends automatically.
 * (C) If you distribute any portion of the software, you must retain all copyright, patent, 
 *     trademark, and attribution notices that are present in the software.
 * (D) If you distribute any portion of the software in source code form, you may do so only under 
 *     this license by including a complete copy of this license with your distribution. If you 
 *     distribute any portion of the software in compiled or object code form, you may only do so 
 *     under a license that complies with this license.
 * (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no 
 *     express warranties, guarantees or conditions. You may have additional consumer rights under 
 *     your local laws which this license cannot change. To the extent permitted under your local laws, 
 *     the contributors exclude the implied warranties of merchantability, fitness for a particular 
 *     purpose and non-infringement.
 * 
 */
#endregion

using System;
using System.Collections.Generic;
using NPack.Interfaces;

namespace NPack.Matrix
{
    /// <summary>
    /// Provides access to <see cref="Matrix{T}"/> processing operations via a 
    /// configured operations engine.
    /// </summary>
    /// <typeparam name="T">Type of matrix element.</typeparam>
    sealed class MatrixProcessor<T>
        where T : IEquatable<T>, IComparable<T>, IComputable<Double, T>, IConvertible, IFormattable
    {
        private static volatile MatrixProcessor<T> _instance = null;
        private static readonly Object _initSync = new Object();
        private readonly IMatrixOperations<T, Vector<T>, Matrix<T>> _ops;

        static MatrixProcessor() {}

        private MatrixProcessor(IMatrixOperations<T, Vector<T>, Matrix<T>> ops)
        {
            _ops = ops;
        }

        /// <summary>
        /// Gets the singleton instance of a MatrixProcessor.
        /// </summary>
        public static MatrixProcessor<T> Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_initSync)
                    {
                        if (_instance == null)
                        {
                            // and this is where the configuration should go...
                            // but we hard code it for now.
                            LinearFactory<T> factory = new LinearFactory<T>();
                            IMatrixOperations<T, Vector<T>, Matrix<T>> ops = new ClrMatrixOperations<T, Vector<T>, Matrix<T>>(factory);

                            _instance = new MatrixProcessor<T>(ops);
                        }
                    }
                }

                return _instance;
            }
        }

        /// <summary>
        /// Gets the configured operations engine.
        /// </summary>
        public IMatrixOperations<T, Vector<T>, Matrix<T>> Operations
        {
            get { return _ops; }
        }

        public static Matrix<T> Add(Matrix<T> lhs, Matrix<T> rhs)
        {
            return Instance.Operations.Add(lhs, rhs);
        }

        public static Vector<T> Add(Vector<T> lhs, Vector<T> rhs)
        {
            return Instance.Operations.Add(lhs, rhs);
        }

        public static Matrix<T> Subtract(Matrix<T> lhs, Matrix<T> rhs)
        {
            return Instance.Operations.Subtract(lhs, rhs);
        }

        public static Vector<T> Subtract(Vector<T> lhs, Vector<T> rhs)
        {
            return Instance.Operations.Subtract(lhs, rhs);
        }

        public static Matrix<T> Multiply(Matrix<T> lhs, Matrix<T> rhs)
        {
            return Instance.Operations.Multiply(lhs, rhs);
        }

        public static Vector<T> Multiply(Matrix<T> multiplier, Vector<T> columnVector)
        {
            return Instance.Operations.Multiply(multiplier, columnVector);
        }

        public static Vector<T> Multiply(Vector<T> rowVector, Matrix<T> multiplier)
        {
            return Instance.Operations.Multiply(rowVector, multiplier);
        }

        public static IEnumerable<Matrix<T>> Multiply(Matrix<T> lhs, IEnumerable<Matrix<T>> rhs)
        {
            return Instance.Operations.Multiply(lhs, rhs);
        }

        public static IEnumerable<Matrix<T>> Multiply(IEnumerable<Matrix<T>> lhs, Matrix<T> rhs)
        {
            return Instance.Operations.Multiply(lhs, rhs);
        }

        public static IEnumerable<Vector<T>> Multiply(Matrix<T> multiplier, IEnumerable<Vector<T>> columnVectors)
        {
            return Instance.Operations.Multiply(multiplier, columnVectors);
        }

        public static IEnumerable<Vector<T>> Multiply(IEnumerable<Vector<T>> rowVectors, Matrix<T> multiplier)
        {
            return Instance.Operations.Multiply(rowVectors, multiplier);
        }

        public static void Multiply(Matrix<T> multiplier, IEnumerable<T[]> columnVectorsComponents)
        {
            Instance.Operations.Multiply(multiplier, columnVectorsComponents);
        }

        public static void Multiply(IEnumerable<T[]> rowVectorsComponents, Matrix<T> multiplier)
        {
            Instance.Operations.Multiply(rowVectorsComponents, multiplier);
        }

        public static void Multiply(Matrix<T> multiplier, T[] columnVector)
        {
            Instance.Operations.Multiply(multiplier, columnVector);
        }

        public static void Multiply(T[] rowVector, Matrix<T> multiplier)
        {
            Instance.Operations.Multiply(rowVector, multiplier);
        }

        public static Matrix<T> ScalarMultiply(Matrix<T> matrix, T scalar)
        {
            return Instance.Operations.ScalarMultiply(matrix, scalar);
        }

        public static Matrix<T> Invert(Matrix<T> matrix)
        {
            return Instance.Operations.Invert(matrix);
        }

        public static Matrix<T> Negate(Matrix<T> matrix)
        {
            return Instance.Operations.Negate(matrix);
        }

        public static void Rotate(Matrix<T> matrix, Vector<T> axis, Double radians)
        {
            IMatrixOperations<T, Vector<T>, Matrix<T>> ops = Instance.Operations;
            ops.SetMatrix(ops.Rotate(matrix, axis, radians), matrix);
        }

        public static void Scale(Matrix<T> matrix, Vector<T> scaleVector)
        {
            IMatrixOperations<T, Vector<T>, Matrix<T>> ops = Instance.Operations;
            ops.SetMatrix(ops.Scale(matrix, scaleVector), matrix);
        }

        public static void Shear(Matrix<T> matrix, Vector<T> shearVector)
        {
            IMatrixOperations<T, Vector<T>, Matrix<T>> ops = Instance.Operations;
            ops.SetMatrix(ops.Shear(matrix, shearVector), matrix);
        }

        public static void Translate(Matrix<T> affineMatrix, Vector<T> translateVector)
        {
            IMatrixOperations<T, Vector<T>, Matrix<T>> ops = Instance.Operations;
            ops.SetMatrix(ops.Translate(affineMatrix, translateVector), affineMatrix);
        }

        public static Double FrobeniusNorm(Matrix<T> matrix)
        {
            return Instance.Operations.FrobeniusNorm(matrix);
        }

        public static Double OneNorm(Matrix<T> matrix)
        {
            return Instance.Operations.OneNorm(matrix);
        }

        public static Double TwoNorm(Matrix<T> matrix)
        {
            return Instance.Operations.TwoNorm(matrix);
        }

        public static Double InfinityNorm(Matrix<T> matrix)
        {
            return Instance.Operations.InfinityNorm(matrix);
        }

        public static Double Determinant(Matrix<T> matrix)
        {
            return Instance.Operations.Determinant(matrix);
        }

        public static Int32 Rank(Matrix<T> matrix)
        {
            return Instance.Operations.Rank(matrix);
        }

        public static Double Condition(Matrix<T> matrix)
        {
            return Instance.Operations.FrobeniusNorm(matrix);
        }

        public static Double Trace(Matrix<T> matrix)
        {
            return Instance.Operations.FrobeniusNorm(matrix);
        }

        public static Matrix<T> Solve(Matrix<T> a, Matrix<T> b)
        {
            return Instance.Operations.Solve(a, b);
        }

        public static Matrix<T> SolveTranspose(Matrix<T> a, Matrix<T> b)
        {
            return Instance.Operations.SolveTranspose(a, b);
        }

        public static Matrix<T> Transpose(Matrix<T> matrix)
        {
            return Instance.Operations.Transpose(matrix);
        }

        public static void SetMatrix(Matrix<T> source, Matrix<T> target)
        {
            Instance.Operations.SetMatrix(source, target);
        }

        public static ILUDecomposition<T, Matrix<T>> GetLuDecomposition(Matrix<T> matrix)
        {
            return Instance.Operations.GetLUDecomposition(matrix);
        }

        public static IQRDecomposition<T, Matrix<T>> GetQrDecomposition(Matrix<T> matrix)
        {
            return Instance.Operations.GetQRDecomposition(matrix);
        }

        public static ICholeskyDecomposition<T, Matrix<T>> GetCholeskyDecomposition(Matrix<T> matrix)
        {
            return Instance.Operations.GetCholeskyDecomposition(matrix);
        }

        public static ISingularValueDecomposition<T, Matrix<T>> GetSingularValueDecomposition(Matrix<T> matrix)
        {
            return Instance.Operations.GetSingularValueDecomposition(matrix);
        }

        public static IEigenvalueDecomposition<T, Matrix<T>> GetEigenvalueDecomposition(Matrix<T> matrix)
        {
            return Instance.Operations.GetEigenvalueDecomposition(matrix);
        }
    }
}